/* Copyright (c) 2008, RDFfs - Andrea Marchesini <baku@morlardf.net>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#else
# error Use configure; make; make install
#endif

#include <rdffs.h>

/* /
 * --/input
 * ----/source
 * ----/error
 * ----/formats
 * ------/xml
 * ------/turtle
 * ------/ntriples
 *
 * --/subjects
 * ----/1
 * ------/subject
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/type
 * ----------/object <symlink to ../../../../../something>
 * --------/2
 * ----------/predicate
 * ----------/language
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 *
 * ----/2
 * ------/subject
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/language
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 *
 * --/bnodes
 * ----/1
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/type
 * ----------/object <symlink to ../../../../../something>
 * --------/2
 * ----------/predicate
 * ----------/language
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 *
 * ----/2
 * ------/triples
 * --------/1
 * ----------/predicate
 * ----------/language
 * ----------/datatype
 * ----------/type
 * ----------/object <text file>
 */

enum rdffs_path_t
{
  RDF_PATH_ERROR,

  RDF_PATH_DIR,
  RDF_PATH_VERSION,
  RDF_PATH_INPUT_DIR,
  RDF_PATH_INPUT_SOURCE,
  RDF_PATH_INPUT_ERROR,
  RDF_PATH_INPUT_FORMATS_DIR,
  RDF_PATH_INPUT_FORMATS_XML,
  RDF_PATH_INPUT_FORMATS_TURTLE,
  RDF_PATH_INPUT_FORMATS_NTRIPLES,
  RDF_PATH_SUBJECTS_DIR,
  RDF_PATH_BNODES_DIR,
  RDF_PATH_SUBJECT_NUMBER_DIR,
  RDF_PATH_BNODE_NUMBER_DIR,
  RDF_PATH_SUBJECT_FILE,
  RDF_PATH_TRIPLE_DIR,
  RDF_PATH_TRIPLE_NUMBER_DIR,
  RDF_PATH_TRIPLE_PREDICATE,
  RDF_PATH_TRIPLE_LANG,
  RDF_PATH_TRIPLE_DATATYPE,
  RDF_PATH_TRIPLE_TYPE,
  RDF_PATH_TRIPLE_OBJECT
};

static gint
rdffs_number (gchar * string)
{
  gunichar ch;
  gchar *text = string;

  while ((ch = g_utf8_get_char (text)))
    {
      text = g_utf8_next_char (text);

      if (g_unichar_isalnum (ch) == FALSE)
	return -1;
    }

  return atoi (string);
}

static enum rdffs_path_t
rdffs_find_path (const char *path, gpointer * something)
{
  gboolean bnode;
  gchar **split;
  gint number;
  GList *list;

  struct rdffs_t *rdf;
  struct rdffs_triple_t *triple;

  /* Path: / */
  if (!strcmp (path, "/"))
    return RDF_PATH_DIR;

  /* Path: /input */
  if (!strcmp (path, "/" RDF_INPUT_DIR))
    return RDF_PATH_INPUT_DIR;

  /* Path: /version */
  if (!strcmp (path, "/" RDF_VERSION_FILE))
    return RDF_PATH_VERSION;

  /* Path: /subjects */
  if (!strcmp (path, "/" RDF_SUBJECT_DIR))
    return RDF_PATH_SUBJECTS_DIR;

  /* Path: /bnodes */
  if (!strcmp (path, "/" RDF_BNODE_DIR))
    return RDF_PATH_BNODES_DIR;

  /* Split the path: */
  if (!(split = g_strsplit (path, "/", -1)))
    return RDF_PATH_ERROR;

  if (*split[0])
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /input/... */
  if (!strcmp (split[1], RDF_INPUT_DIR))
    {

      /* Path: /input/formats/ */
      if (!strcmp (split[2], RDF_INPUT_FORMATS_DIR))
	{
	  if (!split[3])
	    {
	      g_strfreev (split);
	      return RDF_PATH_INPUT_FORMATS_DIR;
	    }

	  if (split[4])
	    {
	      g_strfreev (split);
	      return RDF_PATH_ERROR;
	    }

	  /* Path: /input/formats/xml */
	  if (!strcmp (split[3], RDF_INPUT_FORMATS_XML_FILE))
	    {
	      g_strfreev (split);
	      return RDF_PATH_INPUT_FORMATS_XML;
	    }

	  /* Path: /input/formats/turtle */
	  if (!strcmp (split[3], RDF_INPUT_FORMATS_TURTLE_FILE))
	    {
	      g_strfreev (split);
	      return RDF_PATH_INPUT_FORMATS_TURTLE;
	    }

	  /* Path: /input/formats/ntriples */
	  if (!strcmp (split[3], RDF_INPUT_FORMATS_NTRIPLES_FILE))
	    {
	      g_strfreev (split);
	      return RDF_PATH_INPUT_FORMATS_NTRIPLES;
	    }
	}

      /* Path: /input/<something>/... */
      if (split[3])
	{
	  g_strfreev (split);
	  return RDF_PATH_ERROR;
	}

      /* Path: /input/source */
      if (!strcmp (split[2], RDF_INPUT_SOURCE_FILE))
	{
	  g_strfreev (split);
	  return RDF_PATH_INPUT_SOURCE;
	}

      /* Path: /input/error */
      if (!strcmp (split[2], RDF_INPUT_ERROR_FILE))
	{
	  g_strfreev (split);
	  return RDF_PATH_INPUT_ERROR;
	}

      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /subjects/... */
  if (!strcmp (split[1], RDF_SUBJECT_DIR))
    {
      bnode = FALSE;
      list = rdffs_input.subjects;
    }

  /* Path: /bnodes/... */
  else if (!strcmp (split[1], RDF_BNODE_DIR))
    {
      bnode = TRUE;
      list = rdffs_input.bnodes;
    }

  else
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /<something>/number ? */
  if ((number = rdffs_number (split[2])) < 0)
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* The number doesn't exist */
  if (!(rdf = g_list_nth_data (list, number)))
    {
      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /<something>/number */
  if (!split[3])
    {
      g_strfreev (split);
      *something = rdf;
      return bnode ==
	TRUE ? RDF_PATH_BNODE_NUMBER_DIR : RDF_PATH_SUBJECT_NUMBER_DIR;
    }

  /* Path: /<something>/number/subject */
  if (!strcmp (split[3], RDF_SUBJECT_FILE) && bnode == FALSE)
    {
      if (!split[4])
	{
	  g_strfreev (split);
	  *something = rdf;
	  return RDF_PATH_SUBJECT_FILE;
	}

      g_strfreev (split);
      return RDF_PATH_ERROR;
    }

  /* Path: /<something>/number/triples */
  if (!strcmp (split[3], RDF_TRIPLES_DIR))
    {
      if (!split[4])
	{
	  g_strfreev (split);
	  *something = rdf;
	  return RDF_PATH_TRIPLE_DIR;
	}

      /* Path: /<something>/number/triples/number ? */
      if ((number = rdffs_number (split[4])) < 0)
	{
	  g_strfreev (split);
	  return RDF_PATH_ERROR;
	}

      /* This number exists... */
      if (!(triple = g_list_nth_data (rdf->triples, number)))
	{
	  g_strfreev (split);
	  return RDF_PATH_ERROR;
	}

      /* Path: /<something>/number/triples/number */
      if (!split[5])
	{
	  g_strfreev (split);
	  *something = triple;
	  return RDF_PATH_TRIPLE_NUMBER_DIR;
	}

      /* Path: /<something>/number/triples/number/predicate */
      if (!strcmp (split[5], RDF_PREDICATE_FILE))
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_PREDICATE;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/language */
      if (!strcmp (split[5], RDF_LANG_FILE) && triple->lang)
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_LANG;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/datatype */
      if (!strcmp (split[5], RDF_DATATYPE_FILE) && triple->datatype)
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_DATATYPE;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/type */
      if (!strcmp (split[5], RDF_TYPE_FILE))
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_TYPE;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}

      /* Path: /<something>/number/triples/number/object */
      if (!strcmp (split[5], RDF_OBJECT_FILE))
	{
	  if (!split[6])
	    {
	      g_strfreev (split);
	      *something = triple;
	      return RDF_PATH_TRIPLE_OBJECT;
	    }

	  g_strfreev (split);
	  return -ENOENT;
	}
    }

  g_strfreev (split);
  return -ENOENT;
}

static int
rdffs_syscall_getattr (const char *path, struct stat *stbuf)
{
  gpointer pointer;
  struct rdffs_t *rdf;
  struct rdffs_triple_t *triple;

  rdffs_lock_reader ();

  memset (stbuf, 0, sizeof (struct stat));

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink =
	2 + 1 /* /subjects */  + 1 /* /bnodes */  + 1 /* input */  +
	1 /* version */ ;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink =
	2 + 1 /* /source */  + 1 /* /error */  + 1 /* /formats */ ;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_SOURCE:
      /* Writable: */
      stbuf->st_mode = S_IFREG | 0644;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdffs_input.input_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_ERROR:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdffs_input.error_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink =
	2 + 1 /* /xml */  + 1 /* /turtle */  + 1 /* /ntriples */ ;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_XML:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdffs_input.format_xml_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_TURTLE:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdffs_input.format_turtle_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdffs_input.format_ntriples_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_VERSION:
      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = sizeof (RDF_VERSION_STRING) - 1;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_SUBJECTS_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = rdffs_input.subjects_size + 2;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_BNODES_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = rdffs_input.bnodes_size + 2;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_SUBJECT_NUMBER_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + 1 /* /subject */  + 1 /* /triples */ ;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_BNODE_NUMBER_DIR:
      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + 1 /* /triples */ ;
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_SUBJECT_FILE:
      rdf = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = rdf->subject_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_DIR:
      rdf = pointer;

      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink = 2 + g_list_length (rdf->triples);
      stbuf->st_size = 4096;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_NUMBER_DIR:
      triple = pointer;

      stbuf->st_mode = S_IFDIR | 0555;
      stbuf->st_nlink =
	2 + 1 /* /predicate */  + 1 /* /object */  + 1 /* /type */ ;
      stbuf->st_size = 4096;

      if (triple->lang)
	stbuf->st_nlink++;

      if (triple->datatype)
	stbuf->st_nlink++;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_PREDICATE:
      triple = pointer;

      if (triple->predicate_link.subject)
	{
	  stbuf->st_mode = S_IFLNK | 0444;
	  stbuf->st_nlink = 1;

	  rdffs_unlock_reader ();
	  return 0;
	}

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = triple->predicate_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_LANG:
      triple = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = triple->lang_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_DATATYPE:
      triple = pointer;

      if (triple->datatype_link.subject)
	{
	  stbuf->st_mode = S_IFLNK | 0444;
	  stbuf->st_nlink = 1;

	  rdffs_unlock_reader ();
	  return 0;
	}

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;
      stbuf->st_size = triple->datatype_size;

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_TYPE:
      triple = pointer;

      stbuf->st_mode = S_IFREG | 0444;
      stbuf->st_nlink = 1;

      switch (triple->object_type)
	{
	case RDF_OBJECT_LITERAL:
	  stbuf->st_size = 7;	/* sizeof (literal) - 1 (the \0) */
	  break;

	case RDF_OBJECT_RESOURCE:
	  stbuf->st_size = 8;	/* sizeof (resource) - 1 (the \0) */
	  break;

	case RDF_OBJECT_BNODE:
	  stbuf->st_size = 5;	/* sizeof (bnode) -1 (the \0) */
	  break;
	}

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_OBJECT:
      triple = pointer;

      switch (triple->object_type)
	{
	case RDF_OBJECT_LITERAL:
	  stbuf->st_mode = S_IFREG | 0444;
	  stbuf->st_nlink = 1;
	  stbuf->st_size = triple->object_size;

	  rdffs_unlock_reader ();
	  return 0;

	case RDF_OBJECT_BNODE:
	case RDF_OBJECT_RESOURCE:
	  if (triple->object_link.subject)
	    {
	      stbuf->st_mode = S_IFLNK | 0444;
	      stbuf->st_nlink = 1;

	      rdffs_unlock_reader ();
	      return 0;
	    }

	  stbuf->st_mode = S_IFREG | 0444;
	  stbuf->st_nlink = 1;
	  stbuf->st_size = triple->object_size;

	  rdffs_unlock_reader ();
	  return 0;
	}
    }

  rdffs_unlock_reader ();
  return -ENOENT;
}

static int
rdffs_syscall_readlink (const char *path, char *buf, size_t bufsiz)
{
  gpointer pointer;
  struct rdffs_triple_t *triple;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
    case RDF_PATH_INPUT_DIR:
    case RDF_PATH_INPUT_SOURCE:
    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_INPUT_FORMATS_DIR:
    case RDF_PATH_INPUT_FORMATS_XML:
    case RDF_PATH_INPUT_FORMATS_TURTLE:
    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
    case RDF_PATH_VERSION:
    case RDF_PATH_SUBJECTS_DIR:
    case RDF_PATH_BNODES_DIR:
    case RDF_PATH_SUBJECT_NUMBER_DIR:
    case RDF_PATH_BNODE_NUMBER_DIR:
    case RDF_PATH_SUBJECT_FILE:
    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -ENOLINK;

    case RDF_PATH_TRIPLE_PREDICATE:
      triple = pointer;

      if (!triple->predicate_link.subject)
	{
	  rdffs_unlock_reader ();
	  return -ENOLINK;
	}

      snprintf (buf, bufsiz, "../../../../%s/%d",
		triple->predicate_link.is_bnode ==
		TRUE ? "bnodes" : "subjects", triple->predicate_link.number);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_LANG:
      rdffs_unlock_reader ();
      return -ENOLINK;

    case RDF_PATH_TRIPLE_DATATYPE:
      triple = pointer;

      if (!triple->datatype_link.subject)
	{
	  rdffs_unlock_reader ();
	  return -ENOLINK;
	}

      snprintf (buf, bufsiz, "../../../../%s/%d",
		triple->datatype_link.is_bnode ==
		TRUE ? "bnodes" : "subjects", triple->datatype_link.number);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_TYPE:
      rdffs_unlock_reader ();
      return -ENOLINK;

    case RDF_PATH_TRIPLE_OBJECT:
      triple = pointer;

      switch (triple->object_type)
	{
	case RDF_OBJECT_LITERAL:
	  rdffs_unlock_reader ();
	  return -ENOLINK;

	case RDF_OBJECT_BNODE:
	case RDF_OBJECT_RESOURCE:
	  if (!triple->object_link.subject)
	    {
	      rdffs_unlock_reader ();
	      return -ENOLINK;
	    }

	  snprintf (buf, bufsiz, "../../../../%s/%d",
		    triple->object_link.is_bnode ==
		    TRUE ? "bnodes" : "subjects", triple->object_link.number);

	  rdffs_unlock_reader ();
	  return 0;
	}
    }

  rdffs_unlock_reader ();
  return -ENOENT;
}

static void
rdffs_syscall_readdir_number (gint len, void *buf, fuse_fill_dir_t filler,
			      off_t offset, struct fuse_file_info *fi)
{
  gint i;
  gchar buffer[1024];

  filler (buf, ".", NULL, 0);
  filler (buf, "..", NULL, 0);

  for (i = 0; i < len; i++)
    {
      snprintf (buffer, sizeof (buffer), "%d", i);
      filler (buf, buffer, NULL, 0);
    }
}

static int
rdffs_syscall_readdir (const char *path, void *buf, fuse_fill_dir_t filler,
		       off_t offset, struct fuse_file_info *fi)
{
  gpointer pointer;
  struct rdffs_t *rdf;
  struct rdffs_triple_t *triple;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_SUBJECT_DIR, NULL, 0);
      filler (buf, RDF_BNODE_DIR, NULL, 0);
      filler (buf, RDF_INPUT_DIR, NULL, 0);
      filler (buf, RDF_VERSION_FILE, NULL, 0);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_DIR:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_INPUT_SOURCE_FILE, NULL, 0);
      filler (buf, RDF_INPUT_ERROR_FILE, NULL, 0);
      filler (buf, RDF_INPUT_FORMATS_DIR, NULL, 0);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_DIR:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_INPUT_FORMATS_XML_FILE, NULL, 0);
      filler (buf, RDF_INPUT_FORMATS_TURTLE_FILE, NULL, 0);
      filler (buf, RDF_INPUT_FORMATS_NTRIPLES_FILE, NULL, 0);

      rdffs_unlock_reader ();
      return 0;


    case RDF_PATH_INPUT_SOURCE:
    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_INPUT_FORMATS_XML:
    case RDF_PATH_INPUT_FORMATS_TURTLE:
    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
      rdffs_unlock_reader ();
      return -ENOTDIR;

    case RDF_PATH_VERSION:
      rdffs_unlock_reader ();
      return -ENOTDIR;

    case RDF_PATH_SUBJECTS_DIR:
      rdffs_syscall_readdir_number (rdffs_input.subjects_size, buf, filler,
				    offset, fi);
      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_BNODES_DIR:
      rdffs_syscall_readdir_number (rdffs_input.bnodes_size, buf, filler,
				    offset, fi);
      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_SUBJECT_NUMBER_DIR:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_SUBJECT_FILE, NULL, 0);
      filler (buf, RDF_TRIPLES_DIR, NULL, 0);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_BNODE_NUMBER_DIR:
      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_TRIPLES_DIR, NULL, 0);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_SUBJECT_FILE:
      rdffs_unlock_reader ();
      return -ENOTDIR;

    case RDF_PATH_TRIPLE_DIR:
      rdf = pointer;
      rdffs_syscall_readdir_number (g_list_length (rdf->triples), buf, filler,
				    offset, fi);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_NUMBER_DIR:
      triple = pointer;

      filler (buf, ".", NULL, 0);
      filler (buf, "..", NULL, 0);
      filler (buf, RDF_PREDICATE_FILE, NULL, 0);
      filler (buf, RDF_OBJECT_FILE, NULL, 0);
      filler (buf, RDF_TYPE_FILE, NULL, 0);

      if (triple->lang)
	filler (buf, RDF_LANG_FILE, NULL, 0);

      if (triple->datatype)
	filler (buf, RDF_DATATYPE_FILE, NULL, 0);

      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      rdffs_unlock_reader ();
      return -ENOTDIR;
    }

  rdffs_unlock_reader ();
  return -ENOENT;
}

static int
rdffs_syscall_truncate (const char *path, off_t offset)
{
  gpointer pointer;
  gchar *tmp;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
    case RDF_PATH_INPUT_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      rdffs_unlock_reader ();
      rdffs_lock_writer ();

      if (!(tmp = g_realloc (rdffs_input.input, offset)))
	{
	  rdffs_unlock_writer ();
	  return -ENOMEM;
	}

      rdffs_input.input = tmp;
      if (offset >= rdffs_input.input_size)
	memset (rdffs_input.input + rdffs_input.input_size, 0,
		offset - rdffs_input.input_size);

      rdffs_input.input_size = offset;

      rdffs_unlock_writer ();
      return 0;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_INPUT_FORMATS_DIR:
    case RDF_PATH_INPUT_FORMATS_XML:
    case RDF_PATH_INPUT_FORMATS_TURTLE:
    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
    case RDF_PATH_VERSION:
      rdffs_unlock_reader ();
      return -EACCES;

    case RDF_PATH_SUBJECTS_DIR:
    case RDF_PATH_BNODES_DIR:
    case RDF_PATH_SUBJECT_NUMBER_DIR:
    case RDF_PATH_BNODE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      rdffs_unlock_reader ();
      return -EACCES;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      rdffs_unlock_reader ();
      return -EACCES;
    }

  rdffs_unlock_reader ();
  return -ENOENT;
}

static int
rdffs_syscall_open (const char *path, struct fuse_file_info *fi)
{
  gpointer pointer;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
    case RDF_PATH_INPUT_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      /* It can be written */
      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_INPUT_FORMATS_XML:
    case RDF_PATH_INPUT_FORMATS_TURTLE:
    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
    case RDF_PATH_VERSION:
      rdffs_unlock_reader ();

      if ((fi->flags & 3) != O_RDONLY)
	return -EACCES;

      return 0;

    case RDF_PATH_SUBJECTS_DIR:
    case RDF_PATH_BNODES_DIR:
    case RDF_PATH_SUBJECT_NUMBER_DIR:
    case RDF_PATH_BNODE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      rdffs_unlock_reader ();

      if ((fi->flags & 3) != O_RDONLY)
	return -EACCES;

      return 0;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      rdffs_unlock_reader ();

      if ((fi->flags & 3) != O_RDONLY)
	return -EACCES;

      return 0;
    }

  rdffs_unlock_reader ();
  return -ENOENT;
}

static int
rdffs_rw_real (gchar ** string, gsize * string_size, char *buf, size_t size,
	       off_t offset, struct fuse_file_info *fi, gboolean read)
{
  /* Read: */
  if (read == TRUE)
    {
      if (offset < *string_size)
	{
	  if (offset + size > *string_size)
	    size = *string_size - offset;

	  memcpy (buf, *string + offset, size);
	}
      else
	size = 0;
    }

  /* Write: */
  else
    {
      if (*string_size <= size + offset)
	{
	  gchar *tmp;
	  gsize tmp_size = offset + size;

	  if (!(tmp = g_realloc (*string, tmp_size)))
	    return -ENOMEM;

	  if (offset >= *string_size)
	    memset (tmp + *string_size, 0, tmp_size - *string_size);

	  *string = tmp;
	  *string_size = tmp_size;
	}

      memcpy (*string + offset, buf, size);
    }

  return size;
}

static int
rdffs_syscall_read (const char *path, char *buf, size_t size, off_t offset,
		    struct fuse_file_info *fi)
{
  int ret;
  gpointer pointer;
  struct rdffs_t *rdf;
  struct rdffs_triple_t *triple;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
    case RDF_PATH_INPUT_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      ret =
	rdffs_rw_real (&rdffs_input.input, &rdffs_input.input_size, buf, size,
		       offset, fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_INPUT_ERROR:
      ret =
	rdffs_rw_real (&rdffs_input.error, &rdffs_input.error_size, buf, size,
		       offset, fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_INPUT_FORMATS_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_FORMATS_XML:
      ret =
	rdffs_rw_real (&rdffs_input.format_xml, &rdffs_input.format_xml_size,
		       buf, size, offset, fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_INPUT_FORMATS_TURTLE:
      ret =
	rdffs_rw_real (&rdffs_input.format_turtle,
		       &rdffs_input.format_turtle_size, buf, size, offset, fi,
		       TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
      ret =
	rdffs_rw_real (&rdffs_input.format_ntriples,
		       &rdffs_input.format_ntriples_size, buf, size, offset,
		       fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_VERSION:
      {
	gchar *tmp = g_strdup (RDF_VERSION_STRING);
	gsize tmp_size = sizeof (RDF_VERSION_STRING) - 1;	/* -1 is for \0 */
	int ret;

	ret = rdffs_rw_real (&tmp, &tmp_size, buf, size, offset, fi, TRUE);
	g_free (tmp);

	rdffs_unlock_reader ();
	return ret;
      }

    case RDF_PATH_SUBJECTS_DIR:
    case RDF_PATH_BNODES_DIR:
    case RDF_PATH_SUBJECT_NUMBER_DIR:
    case RDF_PATH_BNODE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      rdf = pointer;
      ret =
	rdffs_rw_real (&rdf->subject, &rdf->subject_size, buf, size, offset,
		       fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
      triple = pointer;
      ret =
	rdffs_rw_real (&triple->predicate, &triple->predicate_size, buf, size,
		       offset, fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_TRIPLE_LANG:
      triple = pointer;
      ret =
	rdffs_rw_real (&triple->lang, &triple->lang_size, buf, size, offset,
		       fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_TRIPLE_DATATYPE:
      triple = pointer;
      ret =
	rdffs_rw_real (&triple->datatype, &triple->datatype_size, buf, size,
		       offset, fi, TRUE);

      rdffs_unlock_reader ();
      return ret;

    case RDF_PATH_TRIPLE_TYPE:
      {
	gchar *tmp;
	gsize size;

	triple = pointer;
	switch (triple->object_type)
	  {
	  case RDF_OBJECT_LITERAL:
	    tmp = g_strdup ("literal");
	    size = 7;

	    ret = rdffs_rw_real (&tmp, &size, buf, size, offset, fi, TRUE);
	    g_free (tmp);

	    rdffs_unlock_reader ();
	    return ret;

	  case RDF_OBJECT_RESOURCE:
	    tmp = g_strdup ("resource");
	    size = 8;

	    ret = rdffs_rw_real (&tmp, &size, buf, size, offset, fi, TRUE);
	    g_free (tmp);

	    rdffs_unlock_reader ();
	    return ret;

	  case RDF_OBJECT_BNODE:
	    tmp = g_strdup ("bnode");
	    size = 5;

	    ret = rdffs_rw_real (&tmp, &size, buf, size, offset, fi, TRUE);
	    g_free (tmp);

	    rdffs_unlock_reader ();
	    return ret;
	  }

	rdffs_unlock_reader ();
	return -1;
      }

    case RDF_PATH_TRIPLE_OBJECT:
      triple = pointer;
      ret =
	rdffs_rw_real (&triple->object, &triple->object_size, buf, size,
		       offset, fi, TRUE);

      rdffs_unlock_reader ();
      return ret;
    }

  rdffs_unlock_reader ();
  return -1;
}

static int
rdffs_syscall_write (const char *path, const char *buf, size_t size,
		     off_t offset, struct fuse_file_info *fi)
{
  gpointer pointer;
  int ret;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
    case RDF_PATH_INPUT_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      rdffs_unlock_reader ();
      rdffs_lock_writer ();

      ret =
	rdffs_rw_real (&rdffs_input.input, &rdffs_input.input_size,
		       (char *) buf, size, offset, fi, FALSE);

      rdffs_unlock_writer ();
      return ret;

    case RDF_PATH_INPUT_FORMATS_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_INPUT_FORMATS_XML:
    case RDF_PATH_INPUT_FORMATS_TURTLE:
    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
    case RDF_PATH_VERSION:
      rdffs_unlock_reader ();
      return -EACCES;

    case RDF_PATH_SUBJECTS_DIR:
    case RDF_PATH_BNODES_DIR:
    case RDF_PATH_SUBJECT_NUMBER_DIR:
    case RDF_PATH_BNODE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      rdffs_unlock_reader ();
      return -EACCES;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      rdffs_unlock_reader ();
      return -EACCES;
    }

  rdffs_unlock_reader ();
  return -1;
}

static int
rdffs_syscall_flush (const char *path, struct fuse_file_info *fi)
{
  gpointer pointer;

  rdffs_lock_reader ();

  switch (rdffs_find_path (path, &pointer))
    {
    case RDF_PATH_ERROR:
      rdffs_unlock_reader ();
      return -ENOENT;

    case RDF_PATH_DIR:
    case RDF_PATH_INPUT_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_SOURCE:
      rdffs_unlock_reader ();
      rdffs_lock_writer ();

      rdffs_destroy_data ();

      /* Ntriples: */
      if (rdffs_parse
	  (rdffs_input.input, rdffs_input.filename, RDF_FORMAT_XML,
	   &rdffs_input.error, &rdffs_input.error_size) == FALSE &&
	  /*turtle: */
	  rdffs_parse (rdffs_input.input, rdffs_input.filename,
		       RDF_FORMAT_TURTLE, &rdffs_input.error,
		       &rdffs_input.error_size) == FALSE)

	/* Parser: */
	if (rdffs_parse
	    (rdffs_input.input, rdffs_input.filename, RDF_FORMAT_NTRIPLES,
	     &rdffs_input.error, &rdffs_input.error_size) == TRUE)
	  {

	    /* Write the XML buffer: */
	    if (rdffs_write
		(rdffs_input.filename, RDF_FORMAT_XML,
		 &rdffs_input.format_xml, &rdffs_input.format_xml_size,
		 &rdffs_input.error, &rdffs_input.error_size) == TRUE
		/* Write the TURTLE buffer: */
		&& rdffs_write (rdffs_input.filename, RDF_FORMAT_TURTLE,
				&rdffs_input.format_turtle,
				&rdffs_input.format_turtle_size,
				&rdffs_input.error,
				&rdffs_input.error_size) == TRUE)

	      /* Write the NTRIPLES buffer: */
	      rdffs_write (rdffs_input.filename, RDF_FORMAT_NTRIPLES,
			   &rdffs_input.format_turtle,
			   &rdffs_input.format_turtle_size,
			   &rdffs_input.error, &rdffs_input.error_size);
	  }

      rdffs_unlock_writer ();
      return 0;

    case RDF_PATH_INPUT_FORMATS_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_INPUT_ERROR:
    case RDF_PATH_INPUT_FORMATS_XML:
    case RDF_PATH_INPUT_FORMATS_TURTLE:
    case RDF_PATH_INPUT_FORMATS_NTRIPLES:
    case RDF_PATH_VERSION:
      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_SUBJECTS_DIR:
    case RDF_PATH_BNODES_DIR:
    case RDF_PATH_SUBJECT_NUMBER_DIR:
    case RDF_PATH_BNODE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_SUBJECT_FILE:
      rdffs_unlock_reader ();
      return 0;

    case RDF_PATH_TRIPLE_DIR:
    case RDF_PATH_TRIPLE_NUMBER_DIR:
      rdffs_unlock_reader ();
      return -EISDIR;

    case RDF_PATH_TRIPLE_PREDICATE:
    case RDF_PATH_TRIPLE_LANG:
    case RDF_PATH_TRIPLE_DATATYPE:
    case RDF_PATH_TRIPLE_TYPE:
    case RDF_PATH_TRIPLE_OBJECT:
      rdffs_unlock_reader ();
      return 0;
    }

  rdffs_unlock_reader ();
  return -1;
}

void
rdffs_destroy (void *dummy)
{
  rdffs_lock_writer ();

  rdffs_destroy_data ();

  if (rdffs_input.input)
    {
      g_free (rdffs_input.input);
      rdffs_input.input = NULL;
    }

  rdffs_unlock_writer ();

  g_mutex_free (rdffs_input.mutex_reader);
  g_mutex_free (rdffs_input.mutex_writer);
}

void
rdffs_destroy_data (void)
{
  if (rdffs_input.subjects)
    {
      g_list_foreach (rdffs_input.subjects, (GFunc) rdffs_free, NULL);
      g_list_free (rdffs_input.subjects);
      rdffs_input.subjects = NULL;

      rdffs_input.subjects_size = 0;
    }

  if (rdffs_input.bnodes)
    {
      g_list_foreach (rdffs_input.bnodes, (GFunc) rdffs_free, NULL);
      g_list_free (rdffs_input.bnodes);
      rdffs_input.bnodes = NULL;

      rdffs_input.bnodes_size = 0;
    }

  if (rdffs_input.format_xml)
    {
      g_free (rdffs_input.format_xml);
      rdffs_input.format_xml = NULL;

      rdffs_input.format_xml_size = 0;
    }

  if (rdffs_input.format_turtle)
    {
      g_free (rdffs_input.format_turtle);
      rdffs_input.format_turtle = NULL;

      rdffs_input.format_turtle_size = 0;
    }

  if (rdffs_input.format_ntriples)
    {
      g_free (rdffs_input.format_ntriples);
      rdffs_input.format_ntriples = NULL;

      rdffs_input.format_ntriples_size = 0;
    }

  if (rdffs_input.error)
    {
      g_free (rdffs_input.error);
      rdffs_input.error = NULL;

      rdffs_input.error_size = 0;
    }
}

static int
rdffs_syscall_create (const char *path, mode_t mode,
		      struct fuse_file_info *fi)
{
  return rdffs_syscall_open (path, fi);
}

static int
rdffs_syscall_ftruncate (const char *path, off_t offset,
			 struct fuse_file_info *fi)
{
  return rdffs_syscall_truncate (path, offset);
}

static int
rdffs_syscall_fgetattr (const char *path, struct stat *stbuf,
			struct fuse_file_info *fi)
{
  return rdffs_syscall_getattr (path, stbuf);
}

/* FUSE DATA STRUCT ********************************************************/

struct fuse_operations rdffs_oper = {
  .getattr = rdffs_syscall_getattr,
  .readlink = rdffs_syscall_readlink,

  .getdir = NULL,
  .mknod = NULL,
  .unlink = NULL,
  .rmdir = NULL,
  .symlink = NULL,
  .rename = NULL,
  .link = NULL,

  .chmod = NULL,
  .chown = NULL,

  .truncate = rdffs_syscall_truncate,

  .utime = NULL,

  .open = rdffs_syscall_open,
  .read = rdffs_syscall_read,
  .write = rdffs_syscall_write,

  .statfs = NULL,

  .flush = rdffs_syscall_flush,
  .release = NULL,
  .fsync = NULL,

  .setxattr = NULL,
  .getxattr = NULL,
  .listxattr = NULL,
  .removexattr = NULL,

  .opendir = NULL,
  .readdir = rdffs_syscall_readdir,
  .releasedir = NULL,
  .fsyncdir = NULL,

  .init = NULL,
  .destroy = rdffs_destroy,

  .access = NULL,
  .create = rdffs_syscall_create,
  .ftruncate = rdffs_syscall_ftruncate,
  .fgetattr = rdffs_syscall_fgetattr,

  .lock = NULL,
  .utimens = NULL,
  .bmap = NULL
};

/* EOF */
